package hdf.parsers.modis;

import hdf.parsers.AbstractHdfParser;
import hdf.utils.HdfFileHelper;
import hdf.utils.LatLonPair;
import hdf.utils.LatLonRange;
import ncsa.hdf.object.Attribute;
import ncsa.hdf.object.Dataset;
import ncsa.hdf.object.FileFormat;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Created by Ania on 29.03.14.
 */
public class L2SurfaceReflectance extends AbstractHdfParser {
    private static L2SurfaceReflectance ourInstance = new L2SurfaceReflectance();
    private static final String DATASET_PATH = "/MODIS SWATH TYPE L2/Data Fields/";
    private static final String LATITUDE_PATH = "/MODIS SWATH TYPE L2/Geolocation Fields/Latitude";
    private static final String LONGITUDE_PATH = "/MODIS SWATH TYPE L2/Geolocation Fields/Longitude";
    private static final String VALID_RANGE = "valid_range";

    public static L2SurfaceReflectance getInstance() {
        return ourInstance;
    }

    private L2SurfaceReflectance() {
    }

    @Override
    public String getName() {
        return "L2 Surface Reflectance";
    }

    @Override
    public String getDescription() {
        return "Parses MOD09 and MYD09 level 2 surface reflectance data obtained from NASA MODIS project";
    }

    @Override
    public boolean isDatasetSupported(String fullName) {
        return fullName.startsWith(DATASET_PATH);
    }

    @Override
    public BufferedImage convertToImage(String filename, FileFormat fileFormat, String datasetPath, LatLonRange latLonRange) {
        FileFormat file = HdfFileHelper.openFile(filename, fileFormat);
        Dataset latitude = HdfFileHelper.getDatasetByFullName(file, LATITUDE_PATH);
        Dataset longitude = HdfFileHelper.getDatasetByFullName(file, LONGITUDE_PATH);
        List<Dataset> datasets = new ArrayList<Dataset>();
        boolean isRGB = false;
        if (DEFAULT_RGB.equals(datasetPath)) {
            for (String path : getRGBDatasets()) {
                datasets.add(HdfFileHelper.getDatasetByFullName(file, path));
            }
            isRGB = true;
        } else {
            datasets.add(HdfFileHelper.getDatasetByFullName(file, datasetPath));
        }
        BufferedImage img = null;
        try {
            List<double[]> ranges = new ArrayList<double[]>();
            List<short[]> datas = new ArrayList<short[]>();
            for (Dataset dataset : datasets) {
                double[] range = new double[]{0.0, 16000.0};
                /* RANGES FROM VALID RANGE */
                List metadata = dataset.getMetadata();
                for (Object attrObj : metadata) {
                    Attribute attr = (Attribute) attrObj;
                    if (VALID_RANGE.equals(attr.getName())) {
                        short[] values = (short[]) attr.getValue();
                        range[0] = values[0];
                        range[1] = values[1];
                    }
                }
                ranges.add(range);
                datas.add((short[]) dataset.getData());
            }
            final int width = datasets.get(0).getWidth();
            final int height = datasets.get(0).getHeight();

            img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            int[] pixels = new int[width * height];
            for (int i = 0; i < pixels.length; i++) {
                int red, green, blue;
                if (isRGB) {
                    red = applyEnhancement(scaleValue(ranges.get(0)[0], ranges.get(0)[1], datas.get(0)[i], 0, 255));
                    green = applyEnhancement(scaleValue(ranges.get(1)[0], ranges.get(1)[1], datas.get(1)[i], 0, 255));
                    blue = applyEnhancement(scaleValue(ranges.get(2)[0], ranges.get(2)[1], datas.get(2)[i], 0, 255));
                } else {
                    int val = scaleValue(ranges.get(0)[0], ranges.get(0)[1], datas.get(0)[i], 0, 255);
                    red = val;
                    green = val;
                    blue = val;
                }
                Color c = new Color(red, green, blue);
                pixels[i] = c.getRGB();
            }
            img.setRGB(0, 0, width, height, pixels, 0, width);
            if (latLonRange != null) {
                latitude.getData();
                longitude.getData();
                int latitudeWidth = latitude.getWidth();
                final int scale = width / latitudeWidth;
                Rectangle croppedImgBounds = getCroppedImgBounds(latitude, longitude, latLonRange, scale);
                img = img.getSubimage(croppedImgBounds.x, croppedImgBounds.y, croppedImgBounds.width, croppedImgBounds.height);
            }
            HdfFileHelper.closeFile(file);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return img;
    }

    private Rectangle getCroppedImgBounds(Dataset latitude, Dataset longitude, LatLonRange latLonRange, int scale) {
        Rectangle bounds = new Rectangle(0, 0, latitude.getWidth() * scale, latitude.getHeight() * scale);
        try {
            int latitudeWidth = latitude.getWidth();
            int latitudeHeight = latitude.getHeight();
            int longitudeWidth = longitude.getWidth();
            int longitudeHeight = longitude.getHeight();
            Object latObj = latitude.read();
            Object lonObj = longitude.read();
            float[] latData = (float[]) latObj;
            float[] lonData = (float[]) lonObj;
            loopX:
            for (int i = 0; i < longitudeWidth; i++) {
                for (int j = 0; j < longitudeHeight; j++) {
                    if (lonData[j * longitudeWidth + i] >= latLonRange.getMin().getLon()) {
                        bounds.x = i * scale;
                        break loopX;
                    }
                }
            }
            loopWidth:
            for (int i = longitudeWidth - 1; i >= 0; i--) {
                for (int j = 0; j < longitudeHeight; j++) {
                    if (lonData[j * longitudeWidth + i] <= latLonRange.getMax().getLon()) {
                        bounds.width = i * scale - bounds.x;
                        break loopWidth;
                    }
                }
            }
            loopY:
            for (int i = 0; i < latitudeHeight; i++) {
                for (int j = 0; j < latitudeWidth; j++) {
                    if (latData[i * latitudeWidth + j] <= latLonRange.getMax().getLat()) {
                        bounds.y = i * scale;
                        break loopY;
                    }
                }
            }
            loopHeight:
            for (int i = latitudeHeight - 1; i >= 0; i--) {
                for (int j = 0; j < latitudeWidth; j++) {
                    if (latData[i * latitudeWidth + j] >= latLonRange.getMin().getLat()) {
                        bounds.height = i * scale - bounds.y;
                        break loopHeight;
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return bounds;
    }

    private int applyEnhancement(int value) {
        final double[] input = new double[]{0, 30, 60, 120, 190, 255};
        final double[] output = new double[]{0, 110, 160, 210, 240, 255};

        for (int i = 0, j = 1; j < input.length; i++, j++) {
            if (value >= input[i] && value < input[j]) {
                return scaleValue(input[i], input[j], value, output[i], output[j]);
            }
        }
        return value;
    }

    private int scaleValue(double minIn, double maxIn, double value, double minOut, double maxOut) {
        if (value < minIn) value = minIn;
        else if (value > maxIn) value = maxIn;
        double percent = (value - minIn) / (maxIn - minIn);
        return (int) (minOut + Math.floor(percent * (maxOut - minOut)));
    }

    @Override
    public List<String> getRGBDatasets() {
        return Arrays.asList(
                DATASET_PATH + "500m Surface Reflectance Band 1",
                DATASET_PATH + "500m Surface Reflectance Band 4",
                DATASET_PATH + "500m Surface Reflectance Band 3");
    }

    @Override
    public LatLonRange findLatLonRange(FileFormat openedFile) {
        Dataset latitude = HdfFileHelper.getDatasetByFullName(openedFile, LATITUDE_PATH);
        Dataset longitude = HdfFileHelper.getDatasetByFullName(openedFile, LONGITUDE_PATH);
        LatLonRange range = null;
        try {
            latitude.getData();
            longitude.getData();
            int latitudeWidth = latitude.getWidth();
            int latitudeHeight = latitude.getHeight();
            int longitudeWidth = longitude.getWidth();
            int longitudeHeight = longitude.getHeight();
            Object latObj = latitude.read();
            Object lonObj = longitude.read();
            float[] latData = (float[]) latObj;
            float[] lonData = (float[]) lonObj;
            float minLat = Float.MAX_VALUE, maxLat = Float.MIN_VALUE, minLon = Float.MAX_VALUE, maxLon = Float.MIN_VALUE;
            for (int i = 0; i < latitudeWidth; i++) {
                if (latData[i] > maxLat) maxLat = latData[i];
                if (latData[(latitudeHeight - 1) * latitudeWidth + i] < minLat)
                    minLat = latData[(latitudeHeight - 1) * latitudeWidth + i];
            }
            for (int i = 0; i < longitudeHeight; i++) {
                if (lonData[i * longitudeWidth] < minLon) minLon = lonData[i * longitudeWidth];
                if (lonData[(i * longitudeWidth) + (longitudeWidth - 1)] > maxLon)
                    maxLon = lonData[(i * longitudeWidth) + (longitudeWidth - 1)];
            }
            LatLonPair min = new LatLonPair(minLat, minLon);
            LatLonPair max = new LatLonPair(maxLat, maxLon);
            range = new LatLonRange(min, max);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return range;
    }
}
